package projectx.core.dao.impl;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import projectx.core.dao.Parameter;

/**
 * <dl>
 * <dt><b>クラス概要：</b></dt><dd>
 * Statement用のSQLビルダー・クラスです。<br>
 * このクラスは次の構文規則に基づいてSQLテンプレート文字列を解析し、
 * SQLテンプレートの構文要素リストを内部で保持します。<br>
 * &lt;SQLTemplate&gt; ::= (&lt;Variable&gt; | &lt;WhereAnd&gt; | &lt;WhereOr&gt; | &lt;Constant&gt;)* <br>
 * &lt;Variable&gt; ::= :[_a-zA-Z0-9]* <br>
 * &lt;WhereAnd&gt; ::= :_WHERE_AND(&lt;Condition&gt;* (, &lt;Condition&gt;)*)<br>
 * &lt;WhereOr&gt; ::= :_WHERE_OR(&lt;Condition&gt;* (, &lt;Condition&gt;)*)<br>
 * &lt;Condition&gt; ::= &lt;VariableCondition&gt; | &lt;ConstantCondition&gt;<br>
 * &lt;VariableCondition&gt; ::= &lt;IsNullableCondition&gt; | &lt;IsNotNullableCondition&gt; | &lt;OtherCondition&gt;<br>
 * &lt;IsNullableCondition&gt; ::= &lt;Constant&gt;* = &lt;Variable&gt; &lt;Constant&gt;*<br>
 * &lt;IsNotNullableCondition&gt; ::= &lt;Constant&gt;* (!= | &lt;&gt; | ^=) &lt;Variable&gt; &lt;Constant&gt;*<br>
 * &lt;OtherCondition&gt; ::= &lt;Constant&gt;* &lt;Variable&gt; &lt;Constant&gt;*<br>
 * &lt;ConstantCondition&gt; ::= &lt;Constant&gt;*<br>
 * &lt;Constant&gt; ::= &lt;Variable&gt;以外の文字列 <br>
 * *1." "(スペース)は無視されます。 <br>
 * *2.":"(コロン)は変数の開始を表す特別な文字です。 <br>
 * *3."'"(シングル・クォーテーション)、
 * """(ダブル・クォーテーション)で囲まれた範囲では、
 * " "および":"が通常の文字として認識されます。 <br>
 * *4."'"で囲まれた範囲内に"'"を含めたい場合は、
 * "'"を２つ続けて記述("''")してください。<br>
 * <br>
 * 各要素の意味は次のとおりです。<br>
 * <table>
 * <thead>
 * <tr>
 * <td>要素</td>
 * <td>概要</td>
 * <td>例</td>
 * </tr>
 * </thead>
 * <tbody>
 * <tr>
 * <td>SQLTemplate</td>
 * <td>SQLテンプレート全体を表す要素です。</td>
 * <td>UPDATE EMPLOYEE SET LASTNAME = :NEWNAME
 *  :_WHERE_AND(EMPNO = :EMPNO, FIRSTNME &lt;&gt; :FIRSTNME, LASTNAME LIKE :LASTNAME, SEX = 'M')</td> 
 * </tr>
 * <tr>
 * <td>Variable</td>
 * <td>変数要素です。SQL発行時に指定されたパラメーターの値によって置換されます。</td>
 * <td>:NEWNAME, :EMPNO, :FIRSTNME, :LASTNAME</td>
 * </tr>
 * <tr>
 * <td>WhereAnd</td>
 * <td>WhereAnd要素です。複数の条件から構成されます。SQL発行時に指定されたパラメーターの値に応じて、
 * 条件を"AND"で連結します。</td>
 * <td>:_WHERE_AND(EMPNO = :EMPNO, FIRSTNME &lt;&gt; :FIRSTNME, LASTNAME LIKE :LASTNAME, SEX = 'M')</td>
 * </tr>
 * <tr>
 * <td>WhereOr</td>
 * <td>WhereOr要素です。複数の条件から構成されます。SQL発行時に指定されたパラメーターの値に応じて、
 * 条件を"OR"で連結します。</td>
 * <td>:_WHERE_OR(EMPNO = :EMPNO, FIRSTNME &lt;&gt; :FIRSTNME, LASTNAME LIKE :LASTNAME, SEX = 'M')</td>
 * </tr>
 * <tr>
 * <td>Condition</td>
 * <td>条件要素です。可変/不変条件要素の抽象要素です。</td>
 * <td>EMPNO = :EMPNO, FIRSTNME &lt;&gt; :FIRSTNME, LASTNAME LIKE :LASTNAME, SEX = 'M'</td>
 * </tr>
 * <tr>
 * <td>VariableCondition</td>
 * <td>可変条件要素です。IS NULL/IS NOT NULL/通常条件要素の抽象要素です。</td>
 * <td>EMPNO = :EMPNO, FIRSTNME &lt;&gt; :FIRSTNME, LASTNAME LIKE :LASTNAME</td>
 * </tr>
 * <tr>
 * <td>IsNullableCondition</td>
 * <td>IS NULL条件要素です。パラメーターの値がnullである場合は、"="を"IS NULL"に置換します。</td>
 * <td>EMPNO = :EMPNO</td>
 * </tr>
 * <tr>
 * <td>IsNotNullableCondition</td>
 * <td>IS NOT NULL条件要素です。
 * パラメーターの値がnullである場合は、"!="/"&lt;&gt;"/"^="を"IS NOT NULL"に置換します。</td>
 * <td>FIRSTNME &lt;&gt; :FIRSTNME</td>
 * </tr>
 * <tr>
 * <td>OtherCondition</td>
 * <td>通常条件要素です。IsNullable/IsNotNullableのどちらにも該当しない可変条件要素です。</td>
 * <td>LASTNAME LIKE :LASTNAME</td>
 * </tr>
 * <tr>
 * <td>ConstantCondition</td>
 * <td>不変条件要素です。パラメーターの値によらず、常に適用されます。</td>
 * <td>SEX = 'M'</td>
 * </tr>
 * <tr>
 * <td>Constant</td>
 * <td>定数要素です。変数/WhereAnd/WhereOr要素に該当しない文字列です。</td>
 * <td>UPDATE,EMPLOYEE,SET,LASTNAME,=,,, </td>
 * </tr>
 * </tbody>
 * </table>
 * <br>
 * 変換例<br>
 * <ul>
 * <li>SQLテンプレート:UPDATE EMPLOYEE SET LASTNAME = :NEWNAME :_WHERE_AND(EMPNO = :EMPNO, FIRSTNME = :FIRSTNME)</li>
 * <li>パラメーター名:NEWNAME,EMPNO</li>
 * <li>パラメーター値:O'CONNELL,000010</li>
 * <li>変換後のSQL:UPDATE EMPLOYEE SET LASTNAME = 'O''CONNELL' WHERE EMPNO = '000010'</li>
 * </ul>
 * このファイルはDAO Generator V1.3.2によって自動生成されました。
 * </dd>
 * </dl>
 */
public class StatementBuilder {
	
	/**
	 * SQLテンプレート上に変数が存在し、かつ、
	 * パラメーターが存在しない場合の例外メッセージです。
	 */
	public static final String MSG_PARAMETER_NOT_FOUND =
		"パラメーターがセットされていません。({0})";
	
	/**
	 * WhereAnd/WhereOr要素内に複数の変数が定義されていた場合の例外メッセージです。
	 */
	public static final String MSG_PLURAL_VARIABLE =
		"一つの条件内に複数の変数を定義することはできません。";
	
	/**
	 * WhereAnd/WhereOr要素の終端記号が存在しない場合の例外メッセージです。
	 */
	public static final String MSG_UNCLOSED_PARENTHESIS =
		"条件の終端記号\")\"が存在しません。";
	
	/**
	 * "'"(シングル・クォーテーション)です。
	 */
	private static final String SINGLEQUOTE = "'";
	
	/**
	 * "''"(二つのシングル・クォーテーション)です。
	 */
	private static final String DOUBLE_SINGLEQUOTE = "''";
	
	/**
	 * "1"です。
	 */
	private static final String ONE = "1";
	
	/**
	 * "0"です。
	 */
	private static final String ZERO = "0";
	
	/**
	 * " "(スペース)です。
	 */
	private static final String SPACE = " ";
	
	/**
	 * "NULL"です。
	 */
	private static final String NULL = "NULL";
	
	/**
	 * SQLテンプレート構文要素のリストです。
	 */
	private List _sqlElements = null;
	
	/**
	 * SQLテンプレートです。
	 */
	private String _sqlTemplate = null;
	
	/**
	 * 不変SQL(WhereAnd/WhereOr/Variableを含まない)フラグです。
	 */
	private boolean _invariable = true;
	
	/**
	 * 不変SQL(WhereAnd/WhereOr/Variableを含まない)です。
	 */
	private String _invariableSql = null;
	
	/**
	 * <dl>
	 * <dt><b>コンストラクター概要：</b></dt><dd>
	 * コンストラクターです。<br>
	 * SQLテンプレートの文字列を、 SQL要素クラスのリストに解析します。<br>
	 * </dd>
	 * </dl>
	 * @param sqlTemplate SQLテンプレート
	 * @exception IllegalArgumentException 解析エラーが発生した場合
	 */
	public StatementBuilder(String sqlTemplate) throws
		IllegalArgumentException {
		
		this._sqlTemplate = sqlTemplate;
		this._sqlElements = new Parser().parse();
		
		// SQLを事前に組み立てる
		// ただし、WhereAnd/WhereOr/Variable要素が存在する場合は組み立てを中止する
		StringBuffer sql = new StringBuffer();
		
		int elementSize = _sqlElements.size();
		
		for (int i = 0; i < elementSize; i++) {
			Element e = (Element) _sqlElements.get(i);
			if (e instanceof Constant) {
				sql.append(e.format(null));
			} else {
				_invariable = false;
				return;
			}
			if (i != _sqlElements.size() - 1) {
				sql.append(SPACE);
			}
		}
		this._invariableSql = sql.toString();
	}
	
	/**
	 * <dl>
	 * <dt><b>メソッド概要：</b></dt><dd>
	 * SQLテンプレートを取得します。
	 * </dd>
	 * </dl>
	 * @return SQLテンプレート
	 */
	public String getSqlTemplate() {
		return _sqlTemplate;
	}
	
	/**
	 * <dl>
	 * <dt><b>メソッド概要：</b></dt><dd>
	 * SQLテンプレートと指定されたパラメーターから、SQLを組み立てます。 <br>
	 * この実装ではパラメーター値がString型である場合、
	 * 値の前後に"'"(シングル・クォーテーション)を付加します。
	 * また、値に"'"が含まれる場合は"''"に置換します。<br>
	 * 値がnullである場合は"NULL"を返します。 <br>
	 * </dd>
	 * </dl>
	 * @param param パラメーター
	 * @return SQL
	 * @exception IllegalArgumentException パラメーター値が不足している場合
	 */
	public String getSql(Parameter param) throws IllegalArgumentException {
		
		// WhereAnd/WhereOr/Variableを含まない場合は不変SQLを返す
		if (_invariable) {
			return _invariableSql;
		}
		StringBuffer sql = new StringBuffer(_sqlTemplate.length());
		
		int elementSize = _sqlElements.size();
		
		for (int i = 0; i < elementSize; i++) {
			sql.append(
				((Element) _sqlElements.get(i)).format(param));
			if (i != elementSize - 1) {
				sql.append(SPACE);
			}
		}
		return sql.toString();
	}
	
	/**
	 * <dl>
	 * <dt><b>メソッド概要：</b></dt><dd>
	 * スクロール可否/更新可否を判定し、Statementインスタンスを生成します。
	 * </dd>
	 * </dl>
	 * @param con Connectionインスタンス
	 * @param scrollable スクロール可否
	 * @param updatable 更新可否
	 * @return Statementインスタンス
	 * @exception SQLException SQLエラーが発生した場合
	 */
	public Statement createStatement(Connection con,
			boolean scrollable, boolean updatable) throws SQLException {
		int typeScroll = scrollable ?
				ResultSet.TYPE_SCROLL_INSENSITIVE : ResultSet.TYPE_FORWARD_ONLY;
		int typeUpdatable = updatable ?
				ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY;
		return con.createStatement(typeScroll, typeUpdatable);
	}
	
	/**
	 * <dl>
	 * <dt><b>メソッド概要：</b></dt><dd>
	 * 引数がString/Date/Time/Timestamp型である場合は
	 * 値の前後に"'"(シングルクォーテーション)を追加します。<br>
	 * 引数がBoolean型である場合は1(true)/0(false)に変換します。<br>
	 * 値がnullである場合は"NULL"を返します。 <br>
	 * 引数がString型である場合は"'"(シングル・クォーテーション)を
	 * 二つのシングル・クォーテーション("''")に置換します。
	 * </dd>
	 * </dl>
	 * @param sqlType SQLデータ型
	 * @param value パラメーター名
	 * @return 変換後文字列
	 */
	protected String escape(int sqlType, Object value) {
		Object tempValue = value;
		if (tempValue == null) {
			return NULL;
		}
		switch (sqlType) {
			case Types.VARCHAR :
				if (tempValue.toString().indexOf(SINGLEQUOTE) != -1) {
					tempValue = tempValue.toString().replaceAll(SINGLEQUOTE, DOUBLE_SINGLEQUOTE);
				}
				StringBuffer sb = new StringBuffer();
				return sb.append(SINGLEQUOTE).append(tempValue).append(SINGLEQUOTE).toString();
			case Types.DATE :
				sb = new StringBuffer();
				return sb.append(SINGLEQUOTE).append(tempValue).append(SINGLEQUOTE).toString();
			case Types.TIME :
				sb = new StringBuffer();
				return sb.append(SINGLEQUOTE).append(tempValue).append(SINGLEQUOTE).toString();
			case Types.TIMESTAMP :
				sb = new StringBuffer();
				return sb.append(SINGLEQUOTE).append(tempValue).append(SINGLEQUOTE).toString();
			case Types.BIT :
				if (((Boolean) tempValue).booleanValue()) {
					return ONE;
				}
				return ZERO;
			default :
				return tempValue.toString();
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * テンプレート構文を構成する要素インターフェースです。
	 * </dd>
	 * </dl>
	 */
	private static interface Element {
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 構文要素文字列を取得します。
		 * </dd>
		 * </dl>
		 * @param param パラメーター
		 * @return 要素文字列
		 * @exception IllegalArgumentException
		 *                要素文字列取得中にエラーが発生した場合
		 */
		String format(Parameter param) throws
			IllegalArgumentException;
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * SQLテンプレートの変数(ex. :FOO)要素クラスです。
	 * </dd>
	 * </dl>
	 */
	private class Variable implements Element {
		
		/**
		 * 変数名です。
		 */
		private String name = null;
		
		/**
		 * <dl>
		 * <dt><b>コンストラクター概要：</b></dt><dd>
		 * コンストラクターです。
		 * </dd>
		 * </dl>
		 * @param name 変数名
		 */
		private Variable(String name) {
			this.name = name;
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 変数名を元にパラメーターから値を取得し、その文字列を返します。 <br>
		 * パラメーター値がString型である場合は、
		 * 値の前後に"'"(シングルクォーテーション)を追加します。<br>
		 * また、値に"'"が含まれる場合は"''"に置換します。
		 * 値がnullである場合は"NULL"を返します。 <br>
		 * </dd>
		 * </dl>
		 * @param param パラメーター
		 * @return パラメーター値の文字列
		 * @exception IllegalArgumentException パラメーターが不足していた場合
		 */
		public String format(Parameter param) {
			if (param != null) {
				String replacement = param.getReplacement(name);
				if (replacement != null) {
					return replacement;
				}
				if (param.containsKey(name)) {
					int sqlType = param.getSqlType(name);
					Object value = param.getObject(name);
					return escape(sqlType, value);
				}
			}
			// パラメーターが指定されていなかった場合
			MessageFormat format = new MessageFormat(MSG_PARAMETER_NOT_FOUND);
			throw new IllegalArgumentException(
					format.format(new Object[] {name}));
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 変数名を取得します。
		 * </dd>
		 * </dl>
		 * @return 変数名
		 */
		private String getName() {
			return name;
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * SQLテンプレートの固定文字列(ex. "SELECT", 'FOO')要素クラスです。
	 * </dd>
	 * </dl>
	 */
	private static class Constant implements Element {
		
		/**
		 * 固定文字列です。
		 */
		private String constant = null;
		
		/**
		 * <dl>
		 * <dt><b>コンストラクター概要：</b></dt><dd>
		 * コンストラクターです。
		 * </dd>
		 * </dl>
		 * @param constant 固定文字列
		 */
		private Constant(String constant) {
			this.constant = constant;
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 固定文字列を返します。
		 * </dd>
		 * </dl>
		 * @param param パラメーター。このメソッドでは使用しません。
		 * @return 固定文字列
		 */
		public String format(Parameter param) {
			return constant;
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * SQLテンプレートのWhereAnd要素クラスです。
	 * 指定されたパラメーターの数に応じて条件をANDで連結します。
	 * </dd>
	 * </dl>
	 */
	private static class WhereAnd implements Element {
		
		/**
		 * 条件のリストです。
		 */
		private List conditions = null;
		
		/**
		 * <dl>
		 * <dt><b>コンストラクター概要：</b></dt><dd>
		 * コンストラクターです。
		 * </dd>
		 * </dl>
		 * @param conditions 条件のリスト
		 */
		private WhereAnd(List conditions) {
			this.conditions = conditions;
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 指定されたパラメーター値の数に応じて、
		 * WHERE句の条件をANDにより連結します。 <br>
		 * </dd>
		 * </dl>
		 * @param param パラメーター
		 * @return ANDによって結合されたWHERE条件
		 * @exception IllegalArgumentException パラメーター値が不足していた場合
		 */
		public String format(Parameter param) throws
			IllegalArgumentException {
			
			//有効な条件のみを抽出
			List validConditions = new ArrayList();
			
			int conditionSize = conditions.size();
			
			for (int i = 0; i < conditionSize; i++) {
				String conditionStr =
					((Condition) conditions.get(i)).format(param);
				if (!"".equals(conditionStr)) {
					validConditions.add(conditionStr);
				}
			}
			//条件が存在しない場合は空文字列を返す
			if (validConditions.isEmpty()) {
				return "";
			}

			StringBuffer sb = new StringBuffer();
			sb.append("WHERE");
			sb.append(SPACE);
			
			int validConditionSize = validConditions.size();
			
			for (int i = 0; i < validConditionSize; i++) {
				sb.append(validConditions.get(i));
				if (i < validConditionSize - 1) {
					sb.append(SPACE);
					sb.append("AND");
					sb.append(SPACE);
				}
			}
			return sb.toString();
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * SQLテンプレートのWhereOr要素クラスです。
	 * 指定されたパラメーターの数に応じて条件をORで連結します。
	 * </dd>
	 * </dl>
	 */
	private static class WhereOr implements Element {
		
		/**
		 * 条件のリストです
		 */
		private List conditions = null;
		
		/**
		 * <dl>
		 * <dt><b>コンストラクター概要：</b></dt><dd>
		 * コンストラクターです。
		 * </dd>
		 * </dl>
		 * @param conditions 条件のリスト
		 */
		private WhereOr(List conditions) {
			this.conditions = conditions;
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 指定されたパラメーター値の数に応じて、
		 * WHERE句の条件をORにより連結します。 <br>
		 * </dd>
		 * </dl>
		 * @param param パラメーター
		 * @return ORによって結合されたWHERE条件
		 * @exception IllegalArgumentException パラメーター値が不足していた場合
		 */
		public String format(Parameter param) {
			//有効な条件のみを抽出
			List validConditions = new ArrayList();
			for (int i = 0; i < conditions.size(); i++) {
				String conditionStr =
					((Condition) conditions.get(i)).format(param);
				if (!"".equals(conditionStr)) {
					validConditions.add(conditionStr);
				}
			}
			//条件が存在しない場合は空文字列を返す
			if (validConditions.isEmpty()) {
				return "";
			}

			StringBuffer sb = new StringBuffer();
			sb.append("WHERE");
			sb.append(SPACE);
			for (int i = 0; i < validConditions.size(); i++) {
				sb.append(validConditions.get(i));
				if (i < validConditions.size() - 1) {
					sb.append(SPACE);
					sb.append("OR");
					sb.append(SPACE);
				}
			}
			return sb.toString();
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * WhereAnd/WhereOr要素に指定するための条件クラスです。 <br>
	 * 変数要素を0個以上含みます。 <br>
	 * パラメーター値がnullであり、
	 * かつ、"= 変数要素"という書式になっている場合は、
	 * "IS NULL"に変換します。 <br>
	 * パラメーター値がnullであり、
	 * かつ、" <>変数要素"または"!= 変数要素"または"^= 変数要素"
	 * という書式になっている場合は、"IS NOT NULL"に変換します。
	 * </dd>
	 * </dl>
	 */
	private static class Condition implements Element {
		
		/**
		 * "="演算子です。
		 */
		private static final String EQUAL = "=";
		
		/**
		 * " <>"演算子です。
		 */
		private static final String NOT_EQUAL1 = "<>";
		
		/**
		 * "!="演算子です。
		 */
		private static final String NOT_EQUAL2 = "!=";
		
		/**
		 * "^="演算子です。
		 */
		private static final String NOT_EQUAL3 = "^=";
		
		/**
		 * 変数要素よりも前の文字列です。
		 */
		private String preConstants = null;
		
		/**
		 * 変数要素です。
		 */
		private Variable variable = null;
		
		/**
		 * 変数要素よりも後の文字列です。
		 */
		private String postConstants = null;
		
		/**
		 * 変数が"IS NULL"の置換候補であるかどうかのフラグです。
		 */
		private boolean isNullable = false;
		
		/**
		 * 変数が"IS NOT NULL"の置換候補であるかどうかのフラグです。
		 */
		private boolean isNotNullable = false;
		
		/**
		 * <dl>
		 * <dt><b>コンストラクター概要：</b></dt><dd>
		 * 引数のリストを解析し、条件に含まれる変数の抽出と
		 * その変数が"IS NULL"/"IS NOT NULL"の置換候補であるかどうかを判定します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 条件に含まれる構文要素のリスト
		 * @exception IllegalArgumentException 一つの条件に複数の変数が含まれている場合
		 */
		private Condition(List sqlElements) throws IllegalArgumentException {
			//SQL要素インデックス
			int i = 0;

			//変数前の文字列を作成
			StringBuffer pre = new StringBuffer();
			while (i < sqlElements.size()) {
				Element e1 = (Element) sqlElements.get(i);
				if (e1 instanceof Variable) {
					variable = (Variable) e1;
					i++;
					break;
				}
				if (e1 instanceof Constant) {
					String value = e1.format(null);
					//最終要素である場合は次の要素を判定しない
					if (i == sqlElements.size() - 1) {
						pre.append(value);
						i++;
						break;
					}
					//要素が"="で、次の要素がVariableであった場合はIsNullable
					if (EQUAL.equals(value)) {
						Element e2 = (Element) sqlElements.get(i + 1);
						if (e2 instanceof Variable) {
							isNullable = true;
							i++;
							continue;
						}
					}
					//要素が"<>","!=","^="で、次の要素がVariableであった場合はIsNotNullable
					if (NOT_EQUAL1.equals(value)
						|| NOT_EQUAL2.equals(value)
						|| NOT_EQUAL3.equals(value)) {
						Element e2 = (Element) sqlElements.get(i + 1);
						if (e2 instanceof Variable) {
							isNotNullable = true;
							i++;
							continue;
						}
					}
					pre.append(value);
					pre.append(SPACE);
				}
				i++;
			}
			preConstants = pre.toString();

			//変数後の文字列を作成
			StringBuffer post = new StringBuffer();
			while (i < sqlElements.size()) {
				Element e1 = (Element) sqlElements.get(i);
				if (e1 instanceof Variable) {
					//条件内に複数の変数を含めることはできない
					throw new IllegalArgumentException(MSG_PLURAL_VARIABLE);
				}
				if (e1 instanceof Constant) {
					post.append(e1.format(null));
				}
				if (i != sqlElements.size() - 1) {
					post.append(SPACE);
				} 
				i++;
			}
			postConstants = post.toString();
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 変数名を元にパラメーターから値を取得し、
		 * 変数をその値で置換した文字列を返します。 <br>
		 * パラメーターが存在しない場合は""(空文字列)を返します。 <br>
		 * 条件に変数が含まれ、かつ、
		 * パラメーターが指定されていない場合は""(空文字列)を返します。 <br>
		 * <br>
		 * 条件に"IS NULL"置換候補の変数が含まれ、
		 * かつ、値がnullである場合は、変数を"IS NULL"に置換した文字列を返します。
		 * <br>
		 * 条件に"IS NOT NULL"置換候補の変数が含まれ、
		 * かつ、値がnullである場合は、変数を"IS NOT NULL"に置換した文字列を返します。 <br>
		 * それ以外の場合で、値がnullである場合は、変数を"NULL"に置換した文字列を返します。
		 * </dd>
		 * </dl>
		 * @param param パラメーター
		 * @return 条件文字列
		 * @exception IllegalArgumentException パラメーターが不足していた場合
		 */
		public String format(Parameter param) throws
			IllegalArgumentException {
			
			StringBuffer sb = new StringBuffer();
			sb.append(preConstants);

			if (variable == null) {
				// 条件に変数が含まれていない場合
				sb.append(postConstants);
				return sb.toString();
			}

			if ((param == null)
				|| ((variable != null)
					&& (!param.containsKey(variable.getName())))) {
				// パラメーターが存在しない場合
				// または、条件に変数が含まれ、かつ、パラメーターが指定されていない場合
				return "";
			}

			Object value = param.getObject(variable.getName());
			if (value == null) {
				if (isNullable) {
					sb.append("IS NULL");
				} else if (isNotNullable) {
					sb.append("IS NOT NULL");
				} else {
					sb.append(variable.format(param));
				}
			} else if (value != null) {
				if (isNullable) {
					sb.append(EQUAL);
					sb.append(SPACE);
				} else if (isNotNullable) {
					sb.append(NOT_EQUAL1);
					sb.append(SPACE);
				}
				sb.append(variable.format(param));
			}
			if (!postConstants.equals("")) {
				sb.append(SPACE);
				sb.append(postConstants);
			}
			return sb.toString();
		}
	}
	
	
	/**
	 * <dl>
	 * <dt><b>クラス概要：</b></dt><dd>
	 * 構文規則に基づいてSQLテンプレート文字列を解析し、
	 * SQLテンプレートの構文要素リストを生成するクラスです。
	 * </dd>
	 * </dl>
	 * @see StatementBuilder
	 */
	private class Parser {
		
		/**
		 * WhereAnd要素の接頭辞です。
		 */
		private static final String WHERE_AND = ":_WHERE_AND(";
		
		/**
		 * WhereOr要素の接頭辞です。
		 */
		private static final String WHERE_OR = ":_WHERE_OR(";
		
		/**
		 * コロンです。
		 */
		private static final char CHAR_COLON = ':';
		
		/**
		 * シングル・クォーテーションです。
		 */
		private static final char CHAR_SINGLEQUOTE = '\'';
		
		/**
		 * ダブル・クオーテーションです。
		 */
		private static final char CHAR_DOUBLEQUOTE = '"';
		
		/**
		 * 空白文字です。
		 */
		private static final char CHAR_SPACE = ' ';
		
		/**
		 * a(小文字)です。
		 */
		private static final char CHAR_LA = 'a';
		
		/**
		 * A(大文字)です。
		 */
		private static final char CHAR_UA = 'A';
		
		/**
		 * z(小文字)です。
		 */
		private static final char CHAR_LZ = 'z';
		
		/**
		 * Z(大文字)です。
		 */
		private static final char CHAR_UZ = 'Z';
		
		/**
		 * 0です。
		 */
		private static final char CHAR_ZERO = '0';
		
		/**
		 * 9です。
		 */
		private static final char CHAR_NINE = '9';
		
		/**
		 * アンダースコアです。
		 */
		private static final char CHAR_UNDERSCORE = '_';
		
		/**
		 * イコール演算子です。
		 */
		private static final char CHAR_EQUAL = '=';
		
		/**
		 * 小なり演算子です。
		 */
		private static final char CHAR_LT = '<';
		
		/**
		 * 大なり演算子です。
		 */
		private static final char CHAR_GT = '>';
		
		/**
		 * NOT演算子です。
		 */
		private static final char CHAR_CARET = '^';
		
		/**
		 * NOT演算子です。
		 */
		private static final char CHAR_EXCLAMATION = '!';
		
		/**
		 * カンマです。
		 */
		private static final char CHAR_COMMA = ',';
		
		/**
		 * 右括弧です。
		 */
		private static final char CHAR_RP = ')';
		
		/**
		 * 比較演算子です。
		 */
		private final char[] COMPARISON_OPERATORS =
			{ CHAR_EQUAL, CHAR_LT, CHAR_GT, CHAR_CARET, CHAR_EXCLAMATION };
		
		/**
		 * SQLテンプレート文字列長です。
		 */
		private int length = _sqlTemplate.length();
		
		/**
		 * 文字の位置インデックスです。
		 */
		private int i = 0;
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 解析処理を実行し、 SQLテンプレートの構文要素リストを生成します。
		 * </dd>
		 * </dl>
		 * @return SQLテンプレートの構文要素リスト
		 */
		private List parse() {
			//SQL要素リスト
			List sqlElements = new ArrayList();

			while (i < length) {
				//単語の開始文字
				char c1 = _sqlTemplate.charAt(i);

				if (c1 == CHAR_COLON) {
					//':'から始まる単語は変数として見なす

					//WHERE_AND構文判定
					if (_sqlTemplate
						.regionMatches(i, WHERE_AND, 0, WHERE_AND.length())) {
						//WHERE_AND構文解析処理
						i += WHERE_AND.length();
						WhereAnd whereAnd = new WhereAnd(parseConditions());
						sqlElements.add(whereAnd);
						continue;
					}

					//WHERE_OR構文判定
					if (_sqlTemplate
						.regionMatches(i, WHERE_OR, 0, WHERE_OR.length())) {
						//WHERE_OR構文解析処理
						i += WHERE_OR.length();
						WhereOr whereOr = new WhereOr(parseConditions());
						sqlElements.add(whereOr);
						continue;
					}

					//通常変数として解析
					addVariable(sqlElements);
					continue;
				} else if (isSingleQuotedLiteral(c1)) {
					//'\''(シングル・クォーテーション)から始まる単語は文字列として見なす
					addSingleQuotedLiteral(sqlElements);
					continue;
				} else if (c1 == CHAR_DOUBLEQUOTE) {
					//'"'(ダブル・クォーテーション)から始まる単語は文字列として見なす
					addDoubleQuoteLiteral(sqlElements);
					continue;
				} else if (c1 == CHAR_SPACE) {
					//' '(空白)は読み飛ばす
					i++;
					continue;
				}
				//文字が':', '\'', '"', ' 'に該当しない場合は、
				//次の区切り文字が来るまで一つの単語として追加し続けて良い
				char[] terminalChars = { CHAR_COLON, CHAR_SINGLEQUOTE, CHAR_DOUBLEQUOTE, CHAR_SPACE };
				addOtherWord(sqlElements, terminalChars);
				continue;
			}
			return sqlElements;
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * WhereAnd/WhereOr要素内の条件を解析し、
		 * 条件のリストを生成します。
		 * </dd>
		 * </dl>
		 * @return 条件リスト
		 * @exception IllegalArgumentException 終端記号')'を検出せずに文字列の最後まで達した場合
		 */
		private List parseConditions() throws IllegalArgumentException {
			//条件リスト
			List conditions = new ArrayList();
			//SQL要素リスト
			List sqlElements = new ArrayList();
			while (i < length) {
				//単語の開始文字
				char c1 = _sqlTemplate.charAt(i);

				if (c1 == CHAR_COLON) {
					//':'から始まる単語は変数として見なす
					addVariable(sqlElements);
					continue;
				} else if (
					(c1 == CHAR_EQUAL)
						|| (c1 == CHAR_LT)
						|| (c1 == CHAR_GT)
						|| (c1 == CHAR_EXCLAMATION)
						|| (c1 == CHAR_CARET)) {
					//'=','<','>','!','^'から始まる単語は比較演算子と見なす
					addComparisonOperator(sqlElements);
					continue;
				} else if (isSingleQuotedLiteral(c1)) {
					//'\''(シングル・クォーテーション)から始まる単語は文字列として見なす
					addSingleQuotedLiteral(sqlElements);
					continue;
				} else if (c1 == CHAR_DOUBLEQUOTE) {
					//'"'(ダブル・クォーテーション)から始まる単語は文字列として見なす
					addDoubleQuoteLiteral(sqlElements);
					continue;
				} else if (c1 == CHAR_SPACE) {
					//' '(空白)は読み飛ばす
					i++;
					continue;
				} else if (c1 == CHAR_COMMA) {
					//','から始まる単語は次の条件の開始と見なす
					i++;
					//条件リストに現在の条件を追加
					Condition condition = new Condition(sqlElements);
					conditions.add(condition);

					//条件を初期化
					sqlElements = new ArrayList();
					continue;
				} else if (c1 == CHAR_RP) {
					//')'はWHERE_ANDの終端記号として見なす
					i++;
					//条件リストに現在の条件を追加
					Condition condition = new Condition(sqlElements);
					conditions.add(condition);
					return conditions;
				}
				//文字が'\'', '"', ':', ' ', ',', ')', '=', '<', '>', '!',
				// '^'に該当しない場合は、
				//次の区切り文字が来るまで一つの単語として追加し続けて良い
				char[] delimiters =
					{ CHAR_COLON, CHAR_EQUAL, CHAR_LT, CHAR_GT,
						CHAR_EXCLAMATION, CHAR_CARET, CHAR_SINGLEQUOTE,
						CHAR_DOUBLEQUOTE, CHAR_SPACE, CHAR_COMMA, CHAR_RP };
				addOtherWord(sqlElements, delimiters);
				continue;
			}
			//終端記号')'を検出せずに文字列の最後まで達した場合
			throw new IllegalArgumentException(MSG_UNCLOSED_PARENTHESIS);
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 変数文字列を解析し、
		 * 構文要素リストに変数要素インスタンスを追加します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 構文要素リスト
		 */
		private void addVariable(List sqlElements) {
			//単語
			StringBuffer word = new StringBuffer();
			//通常変数として解析
			i++;
			//次の半角英数字と"_"以外の文字が来るまで一つの単語として追加し続けて良い
			while (i < length) {
				char c1 = _sqlTemplate.charAt(i);
				if ((c1 < CHAR_LA || c1 > CHAR_LZ)
					&& (c1 < CHAR_UA || c1 > CHAR_UZ)
					&& (c1 < CHAR_ZERO || c1 > CHAR_NINE)
					&& (c1 != CHAR_UNDERSCORE)) {
					break;
				}
				word.append(c1);
				i++;
			}
			sqlElements.add(new Variable(word.toString()));
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * "'"(シングル・クォーテーション)で囲まれた範囲を解析し、
		 * 構文要素リストに固定文字列要素インスタンスを追加します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 構文要素リスト
		 */
		private void addSingleQuotedLiteral(List sqlElements) {
			//単語
			StringBuffer word = new StringBuffer();
			
			//単語の開始文字
			char c1 = _sqlTemplate.charAt(i);
			word.append(c1);
			i++;
			if (((c1 == 'G') || (c1 == 'g'))
					&& (i < length)
					&& (_sqlTemplate.charAt(i) == CHAR_SINGLEQUOTE)) {
				word.append(CHAR_SINGLEQUOTE);
				i++;
			}
			
			//次の'\''が来るまで文字を追加し続けて良い
			//ただし、'\''が２つ並んでいる場合は文字としての'\''と見なす
			while (i < length) {
				char c2 = _sqlTemplate.charAt(i);
				word.append(c2);
				i++;

				if (c2 != CHAR_SINGLEQUOTE) {
					//'\''ではない場合は追加し続ける
					continue;
				}

				if (i >= length) {
					break;
				}
				
				//'\''に遭遇した場合は２つ並んでいるかどうかを確認する
				char c3 = _sqlTemplate.charAt(i);
				if (c3 != CHAR_SINGLEQUOTE) {
					//２つ並んでいない場合は区切り文字として見なしループを抜ける
					break;
				}
				//次の文字も'\''の場合は文字として見なす
				word.append(c3);
				i++;
			}
			sqlElements.add(new Constant(word.toString()));
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * """(ダブル・クォーテーション)で囲まれた範囲を解析し、
		 * 構文要素リストに固定文字列要素インスタンスを追加します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 構文要素リスト
		 */
		private void addDoubleQuoteLiteral(List sqlElements) {
			//単語
			StringBuffer word = new StringBuffer();
			//単語の開始文字
			char c1 = _sqlTemplate.charAt(i);

			//'"'(ダブル・クォーテーション)から始まる単語は文字列として見なす
			word.append(c1);
			i++;
			//次の'"'が来るまで文字を追加し続けて良い
			while (i < length) {
				char c2 = _sqlTemplate.charAt(i);
				word.append(c2);
				i++;
				if (c2 == CHAR_DOUBLEQUOTE) {
					break;
				}
			}
			sqlElements.add(new Constant(word.toString()));
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * 比較演算子を表す文字列を解析し、
		 * 構文要素リストに固定文字列要素インスタンスを追加します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 構文要素リスト
		 */
		private void addComparisonOperator(List sqlElements) {
			//単語
			StringBuffer word = new StringBuffer();
			//単語の開始文字
			char c1 = _sqlTemplate.charAt(i);

			word.append(c1);
			i++;
			//比較演算子以外の文字が来るまで文字を追加しつづけてよい。
			while (i < length) {
				char c2 = _sqlTemplate.charAt(i);
				//比較演算子かどうかを判定
				boolean match = false;
				for (int j = 0; j < COMPARISON_OPERATORS.length; j++) {
					if (c2 == COMPARISON_OPERATORS[j]) {
						match = true;
						break;
					}
				}
				if (!match) {
					break;
				}
				word.append(c2);
				i++;
			}
			sqlElements.add(new Constant(word.toString()));
		}
		
		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * その他の文字列を解析し、
		 * 構文要素リストに固定文字列要素インスタンスを追加します。
		 * </dd>
		 * </dl>
		 * @param sqlElements 構文要素リスト
		 * @param delimiters 区切り文字の配列
		 */
		private void addOtherWord(List sqlElements, char[] delimiters) {
			//単語
			StringBuffer word = new StringBuffer();
			//単語の開始文字
			char c1 = _sqlTemplate.charAt(i);

			word.append(c1);
			i++;
			//次の区切り文字が来るまで文字を追加しつづけてよい。
			while (i < length) {
				char c2 = _sqlTemplate.charAt(i);
				boolean match = false;
				for (int j = 0; j < delimiters.length; j++) {
					if (c2 == delimiters[j]) {
						match = true;
						break;
					}
				}
				if (match) {
					break;
				}
				word.append(c2);
				i++;
			}
			sqlElements.add(new Constant(word.toString()));
		}

		/**
		 * <dl>
		 * <dt><b>メソッド概要：</b></dt><dd>
		 * "'"(シングル・クォーテーション)で囲まれた文字列かどうかを判定します。
		 * GRAPHIC型の書式(例：G'ABC')もこれに該当します。
		 * </dd>
		 * </dl>
		 * @param c1 文字
		 * @return 判定結果
		 */
		private boolean isSingleQuotedLiteral(char c1) {
			if (c1 == CHAR_SINGLEQUOTE) {
				return true;
			}
			return ((c1 == 'G') || (c1 == 'g'))
					&& (i + 1 < length)
					&& (_sqlTemplate.charAt(i + 1) == CHAR_SINGLEQUOTE);
		}
	}
}